﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Services.Client;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace Ogdi.Data.DataLoader
{
    class TableEntityProcessor : EntityProcessor
    {
        private static CloudBlobClient blobClient;
        private TableProcessorParams _params;
        private TableOverwriteMode overwriteMode;

        public TableEntityProcessor(TableProcessorParams parameters, TableOverwriteMode overwriteMode)
        {
            _params = parameters;
            this.overwriteMode = overwriteMode;
        }

        //check that rowkey and partition key columns are listed in metadata
        public override void ValidateParams(Entity schemaEntity)
        {
            schemaEntity.ValidateProperty(_params.PartitionKeyPropertyName);
            schemaEntity.ValidateProperty(_params.RowKeyPropertyName);
        }

        public override void ProcessEntity(string entitySetName, Entity entity)
        {
            StoreEntity(entitySetName, _params.PartitionKeyPropertyName, _params.RowKeyPropertyName, entity);
        }

        public override void ProcessTableMetadataEntity(string entitySetName, Entity entity)
        {
            if (overwriteMode != TableOverwriteMode.Create)
                return;
            StoreEntity(
                entitySetName,
                _params.TableMetadataPartitionKeyPropertyName,
                _params.TableMetadataRowKeyPropertyName,
                entity);
            //store params
            StoreEntity(
                DataLoaderConstants.EntitySetProcessorParams,
                DataLoaderConstants.ValueUniqueAutoGen,
                DataLoaderConstants.ValueUniqueAutoGen,
                _params);
        }

        public override void ProcessEntityMetadataEntity(string entitySetName, Entity entity)
        {
            if (overwriteMode != TableOverwriteMode.Create)
                return;
            StoreEntity(
                entitySetName, 
                _params.EntityMetadataPartitionKeyPropertyName,
                _params.EntityMetadataRowKeyPropertyName, 
                entity);
        }

        private static string ConvertToProperPartitionKey(string parKey)
        {
            var pk = new StringBuilder();
            for (int i = 0; i < parKey.Length; ++i)
            {
                if (char.IsLetterOrDigit(parKey[i]) || parKey[i] == ':' || parKey[i] == '-')
                {
                    pk.Append(parKey[i]);
                }
            }

            return pk.ToString();
        }

        private static string ConvertValueToString(object val)
        {
            if (val.GetType() == typeof(DateTime))
            {
                DateTime dt = (DateTime)val;
                return string.Format("{0:D4}-{1:D2}-{2:D2}{3:D2}:{4:D2}:{5:D2}", dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
            }
            else
            {
                return val.ToString();
            }
        }
        
        private void StoreEntity(string entitySetName, string parKeyPropName, string rowKeyPropName, Entity entity)
        {
            var account = CloudStorageAccount.Parse(ConfigurationSettings.AppSettings["DataConnectionString"]);
            var context = new TableContext(account.TableEndpoint.ToString(), account.Credentials, _params)
                {
                    RetryPolicy = RetryPolicies.RetryExponential(5, new TimeSpan(0,0,1))
                };
            
            string kmlSnippet = (string) entity[DataLoaderConstants.PropNameKmlSnippet];
            if (kmlSnippet != null && kmlSnippet.Length > 32 * 1024)
            {
                var blobName = Guid.NewGuid().ToString();
                var containerName = entitySetName.ToLower();
                StoreKmlSnippetAsBlob(containerName, blobName, kmlSnippet);
                entity[DataLoaderConstants.PropNameKmlSnippet] = string.Format(DataLoaderConstants.KmlSnippetReference, containerName, blobName);
            }

            TableEntity tableEntity = null;
            bool isUpdate = false;
            if (_params != null)
            {                
                string parKey;                
                if (string.IsNullOrEmpty(entity.Number))
                {
                    var pk = entity[parKeyPropName];
                    parKey = (pk != null) ? ConvertToProperPartitionKey(ConvertValueToString(pk)) : entity.Id.ToString();                    
                }
                else
                {                   
                    parKey = entity.Number;                    
                }

                string rowKey = null;
                if (rowKeyPropName.ToLower() == DataLoaderConstants.ValueUniqueAutoGen)
                {
                    rowKey = Guid.NewGuid().ToString();
                }
                else
                {
                    var rk = entity[rowKeyPropName];
                    rowKey = (rk != null) ? ConvertValueToString(entity[rowKeyPropName]) : Guid.NewGuid().ToString();
                }


                //try to load entity from storage
                if (overwriteMode == TableOverwriteMode.Add || overwriteMode == TableOverwriteMode.Update)
                {
                    tableEntity = LoadEntity(context, entitySetName, rowKeyPropName, rowKey, parKeyPropName, parKey);
                    if (tableEntity != null && overwriteMode == TableOverwriteMode.Add)
                        throw new EntityAlreadyExistsException(entitySetName, rowKeyPropName, rowKey, parKeyPropName, parKey);
                    else if (tableEntity != null)
                    {
                        tableEntity.UpdateEntity(entity);
                        isUpdate = true;
                    }
                }
                //if not found, create new
                if (tableEntity == null)
                    tableEntity = new TableEntity(entity, parKey, rowKey);
            }
            else
            {
                tableEntity = new TableEntity(entity);
            }

            if (!isUpdate)
                context.AddObject(entitySetName, tableEntity);
            else
                context.UpdateObject(tableEntity);

            try
            {
                context.SaveChanges();
            }
            catch (StorageClientException e)
            {
                if (e.ErrorCode == StorageErrorCode.ResourceAlreadyExists && e.StatusCode == System.Net.HttpStatusCode.Conflict)
                {
                    throw new DuplicateEntityException(tableEntity.ToString(), e);
                }
            }
            catch (DataServiceRequestException e)
            {
                if (e.InnerException != null && ((DataServiceClientException)e.InnerException).StatusCode == (int)System.Net.HttpStatusCode.Conflict)
                {
                    throw new DuplicateEntityException(tableEntity.ToString(), e);
                }
            }
        }

        private static readonly string s_EntityQueryTemplate = "{0}?$filter={1} eq '{2}' and  {3} eq '{4}'";
         
        private TableEntity LoadEntity(TableContext context, string entitySetName, string RowKeyColumn, string RowKeyValue, string ParKeyColumn, string ParKeyValue)
        {
            var query = string.Format(s_EntityQueryTemplate, entitySetName, RowKeyColumn, RowKeyValue, ParKeyColumn, ParKeyValue);

            var results = context.Execute<TableEntity>(new Uri(query, UriKind.Relative)).ToList();
            if (results.Count == 0)
            {
                return null;
            }
            else if (results.Count > 1)
            {
                throw new DuplicateEntityException(query);
            }
            else 
                return results[0];

        }

        private void StoreKmlSnippetAsBlob(string containerName, string blobName, string kmlSnippet)
        {
            var acc = CloudStorageAccount.Parse(ConfigurationSettings.AppSettings["DataConnectionString"]);
            
            var bc = acc.CreateCloudBlobClient();

            var c = bc.GetContainerReference(containerName);

            if(c.CreateIfNotExist())
            {
                c.SetPermissions(new BlobContainerPermissions() { PublicAccess = BlobContainerPublicAccessType.Container });
            }

            var b = c.GetBlobReference(blobName);

            b.Properties.ContentType = DataLoaderConstants.ContentTypeKmlSnippet;

            b.UploadByteArray(System.Text.Encoding.UTF8.GetBytes(kmlSnippet));
        }
    }
}
